#include "hide_file.h"
#include "xxx_mapping.h"
#include "xxx_info.h"
#include <sys/mount.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libgen.h>
#include <libmount/libmount.h>


/*
 * Create fake entry file of given filepath. The fake filename is the origin filename appended with ".xxx" suffix.
 *  @filepath: origin filepath
 *  return:
 *      on success, the inode number of fake entry file is returned.
 *      on error, return zero.
*/
ext2_ino_t create_shown_file(const char *filepath)
{

    char temp_path[PATH_MAX];
    strncpy(temp_path, filepath, PATH_MAX);
    size_t len = strlen(temp_path);
    strcat(temp_path + len, ".xxx");

    // check filepath exist, shoundn't overwrite
    if (access(temp_path, F_OK) != -1)
        return 0;

    FILE *file = fopen(temp_path, "w");
    if (file == NULL)
        return 0;

    const char content[] = "This is a ext4-xxx shown file. There is a hidden file behind it.\n\n";
    fwrite(content, 1, sizeof(content), file);
    fclose(file);


    struct stat st = {};
    if (stat(temp_path, &st) != 0)
        return 0;

    return (ext2_ino_t)st.st_ino;

}


/*
 * Hide file
 *  @info: correctly loaded xxx_info
 *  @filepath: the file you want to hide
 *  return:
 *      On success, return zero.
*/
// WARNING: This function should be atomic, eithor success or error.
int hide_file(struct xxx_info *info, const char *filepath)
{
    if (info == NULL || info->fs == NULL || filepath == NULL)
        return -1;

    // get full path, dir path, filename
    char dirpath[PATH_MAX];
    char filename[PATH_MAX];

    if (three_in_one(info, filepath, filename, dirpath) != 0)
        return 1;

    struct stat file_st = {};
    if (stat(filepath, &file_st) != 0)
        return 2;

    struct stat dir_st = {};
    if (stat(dirpath, &dir_st) != 0)
        return 4;

    // get a free mapping item
    struct xxx_mapping_item *item = new_mapping(info);
    if (item == NULL)
        return 5;

    // only support regular file and directory
    __u8 filetype;
    if (S_ISREG(file_st.st_mode))
        filetype = EXT2_FT_REG_FILE;
    else if (S_ISDIR(file_st.st_mode))
        filetype = EXT2_FT_DIR;
    else
        return -1; // don't support this file type

    // WARNING: cannot use stdio to operate target fs when it's opened.
    ext2fs_close(info->fs);

    // link: create a new file on this dir, then record mapping
    ext2_ino_t inode = create_shown_file(filepath);

    // reopen fs
    ext2fs_open(info->devpath, EXT2_FLAG_RW, 0, 0, unix_io_manager, &info->fs);

    if (inode == 0)
        return 6;

    // unlink: clear dir entry to this file, but don't change linkcount
    errcode_t err_code = ext2fs_unlink(info->fs, (ext2_ino_t)dir_st.st_ino, NULL, (ext2_ino_t)file_st.st_ino, 0);
    if (err_code != 0)
        return 7;

    // remount, flush Linux kernel cache
    mount("ignore", info->mountpoint, "ignore", MS_REMOUNT, "rw");

    // create mapping

    item->shown_ino_t = inode;
    strncpy(item->hidden_entry.name, filename, 255);
    item->hidden_entry.filetype = filetype;
    item->hidden_entry.hidden_ino_t = (unsigned int)file_st.st_ino;


    return 0;
}
